﻿using System;

namespace Sharpmark.QQ.ChatLog
{
    /// <summary>
    /// QQ Msg En/DeCrypt Class
    /// Writen By Red_angelX On 2006.9.13
    /// Modify by Sharpmark on 2009.04.17
    /// </summary>
    public class QQCrypt
    {
        //QQ TEA-16 Encrypt/Decrypt Class 
        // 
        // 
        //And also LumaQQ//s source code 
        //  CopyRight:No CopyRight^_^ 
        //  Author : Red_angelX     
        //  NetWork is Free,Tencent is ****!
        // 
        //Class Begin 
        //AD:Find Job!!,If you Want Give me a Job,Content Me!!

        //Copied & translated from LumaQQ//s source code          `From LumaQQ///s source code: 
        private byte[] _plain;                          //指向当前的明文块 
        private byte[] _prePlain;                       //指向前面一个明文块 
        private byte[] _out;                            //输出的密文或者明文 
        private long _crypt, _preCrypt;                 //当前加密的密文位置和上一次加密的密文块位置，他们相差8 
        private long _pos;                              //当前处理的加密解密块的位置 
        private long _padding;                          //填充数 
        private readonly byte[] _key = new byte[16];    //密钥 
        private bool _header;                           //用于加密时，表示当前是否是第一个8字节块，因为加密算法 
        //是反馈的，但是最开始的8个字节没有反馈可用，所有需要标明这种情况 
        private long _contextStart;                     //这个表示当前解密开始的位置，之所以要这么一个变量是为了 
        //避免当解密到最后时后面已经没有数据，这时候就会出错，这个变量就是用来判断这种情况免得出错 
        public QQCrypt()
        {
        }


        //Push 数据
        static byte[] CopyMemory(byte[] arr, int arrIndex, long input)  //lenth = 4
        {
            if (arrIndex + 4 > arr.Length)
            {
                // 不能执行
                return arr;
            }

            arr[arrIndex + 3] = (byte)((input & 0xff000000) >> 24);
            arr[arrIndex + 2] = (byte)((input & 0x00ff0000) >> 16);
            arr[arrIndex + 1] = (byte)((input & 0x0000ff00) >> 8);
            arr[arrIndex] = (byte)(input & 0x000000ff);

            arr[arrIndex] &= 0xff;
            arr[arrIndex + 1] &= 0xff;
            arr[arrIndex + 2] &= 0xff;
            arr[arrIndex + 3] &= 0xff;

            return arr;
        }
        /*
        long CopyMemory(long Out, byte[] arr, int arr_index)
        {
            if (arr_index + 4 > arr.Length)
            {
                return Out;
                //不能执行
            }

            long x1 = arr[arr_index + 3] << 24;
            long x2 = arr[arr_index + 2] << 16;
            long x3 = arr[arr_index + 1] << 8;
            long x4 = arr[arr_index];

            long o = x1 | x2 | x3 | x4;
            o &= 0xffffffff;
            return o;
        }
        */
        static long GetUnsignedInt(byte[] arrayIn, int offset, int len /*Default is 4*/)
        {

            long ret = 0;
            int end = 0;
            if (len > 8)
                end = offset + 8;
            else
                end = offset + len;
            for (int i = offset; i < end; i++)
            {
                ret <<= 8;
                ret |= arrayIn[i] & 0xff;
            }
            return (ret & 0xffffffff) | (ret >> 32);
        }

        static long Rand()
        {
            Random rd = new Random();
            return rd.Next() + (rd.Next() % 1024);
        }

        static byte[] Decipher(byte[] arrayIn, byte[] arrayKey, long offset)
        {
            //long Y,z,a,b,c,d;
            //Y=z=a=b=c=d=0;
            byte[] tmpArray = new byte[24];
            byte[] tmpOut = new byte[8];
            if (arrayIn.Length < 8)
            {
                // Error:return
                return tmpOut;
            }
            if (arrayKey.Length < 16)
            {
                // Error:return
                return tmpOut;
            }
            long sum = 0xE3779B90;
            sum = sum & 0xFFFFFFFF;
            long delta = 0x9E3779B9;
            delta = delta & 0xFFFFFFFF;

            /*tmpArray[3] = arrayIn[offset]; 
        tmpArray[2] = arrayIn[offset + 1]; 
        tmpArray[1] = arrayIn[offset + 2]; 
        tmpArray[0] = arrayIn[offset + 3]; 
        tmpArray[7] = arrayIn[offset + 4]; 
        tmpArray[6] = arrayIn[offset + 5]; 
        tmpArray[5] = arrayIn[offset + 6]; 
        tmpArray[4] = arrayIn[offset + 7]; 
        tmpArray[11] = arrayKey[0]; 
        tmpArray[10] = arrayKey[1]; 
        tmpArray[9] = arrayKey[2]; 
        tmpArray[8] = arrayKey[3]; 
        tmpArray[15] = arrayKey[4]; 
        tmpArray[14] = arrayKey[5]; 
        tmpArray[13] = arrayKey[6]; 
        tmpArray[12] = arrayKey[7]; 
        tmpArray[19] = arrayKey[8]; 
        tmpArray[18] = arrayKey[9]; 
        tmpArray[17] = arrayKey[10]; 
        tmpArray[16] = arrayKey[11]; 
        tmpArray[23] = arrayKey[12]; 
        tmpArray[22] = arrayKey[13]; 
        tmpArray[21] = arrayKey[14]; 
        tmpArray[20] = arrayKey[15]; 
        Y=CopyMemory(Y,tmpArray,0);    
        z=CopyMemory(z,tmpArray,4);
        a=CopyMemory(a,tmpArray,8);
        b=CopyMemory(b,tmpArray,12);
        c=CopyMemory(c,tmpArray,16);
        d=CopyMemory(d,tmpArray,20);*/
            long y = GetUnsignedInt(arrayIn, (int)offset, 4);
            long z = GetUnsignedInt(arrayIn, (int)offset + 4, 4);
            long a = GetUnsignedInt(arrayKey, 0, 4);
            long b = GetUnsignedInt(arrayKey, 4, 4);
            long c = GetUnsignedInt(arrayKey, 8, 4);
            long d = GetUnsignedInt(arrayKey, 12, 4);
            for (int i=1; i <= 16; i++)
            {
                z -= ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
                z &= 0xFFFFFFFF;
                y -= ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
                y &= 0xFFFFFFFF;
                sum -= delta;
                sum &= 0xFFFFFFFF;
            }

            tmpArray = CopyMemory(tmpArray, 0, y);
            tmpArray = CopyMemory(tmpArray, 4, z);
            tmpOut[0] = tmpArray[3];
            tmpOut[1] = tmpArray[2];
            tmpOut[2] = tmpArray[1];
            tmpOut[3] = tmpArray[0];
            tmpOut[4] = tmpArray[7];
            tmpOut[5] = tmpArray[6];
            tmpOut[6] = tmpArray[5];
            tmpOut[7] = tmpArray[4];

            return tmpOut;
        }

        private static byte[] Decipher(byte[] arrayIn, byte[] arrayKey)
        {
            return Decipher(arrayIn, arrayKey, 0);
        }

        private static byte[] Encipher(byte[] arrayIn, byte[] arrayKey, long offset)
        {
            byte[] tmpOut = new byte[8];
            byte[] tmpArray = new byte[24];
            //long Y,z,a,b,c,d;
            //Y=z=a=b=c=d=0;
            if (arrayIn.Length < 8)
            {
                // Error:
                return tmpOut;
            }
            if (arrayKey.Length < 16)
            {
                // Error:
                return tmpOut;
            }
            long sum = 0;
            long delta = 0x9E3779B9;
            delta &= 0xFFFFFFFF;


            /*tmpArray[3] = arrayIn[offset]; 
        tmpArray[2] = arrayIn[offset + 1]; 
        tmpArray[1] = arrayIn[offset + 2]; 
        tmpArray[0] = arrayIn[offset + 3]; 
        tmpArray[7] = arrayIn[offset + 4]; 
        tmpArray[6] = arrayIn[offset + 5]; 
        tmpArray[5] = arrayIn[offset + 6]; 
        tmpArray[4] = arrayIn[offset + 7]; 
        tmpArray[11] = arrayKey[0]; 
        tmpArray[10] = arrayKey[1]; 
        tmpArray[9] = arrayKey[2]; 
        tmpArray[8] = arrayKey[3]; 
        tmpArray[15] = arrayKey[4]; 
        tmpArray[14] = arrayKey[5]; 
        tmpArray[13] = arrayKey[6]; 
        tmpArray[12] = arrayKey[7]; 
        tmpArray[19] = arrayKey[8];
        tmpArray[18] = arrayKey[9]; 
        tmpArray[17] = arrayKey[10]; 
        tmpArray[16] = arrayKey[11]; 
        tmpArray[23] = arrayKey[12]; 
        tmpArray[22] = arrayKey[13]; 
        tmpArray[21] = arrayKey[14]; 
        tmpArray[20] = arrayKey[15]; 

        Y=CopyMemory(Y,tmpArray,0);
        z=CopyMemory(z,tmpArray,4);
        a=CopyMemory(a,tmpArray,8);
        b=CopyMemory(b,tmpArray,12);
        c=CopyMemory(c,tmpArray,16);
        d=CopyMemory(d,tmpArray,20);*/

            long y = GetUnsignedInt(arrayIn, (int)offset, 4);
            long z = GetUnsignedInt(arrayIn, (int)offset + 4, 4);
            long a = GetUnsignedInt(arrayKey, 0, 4);
            long b = GetUnsignedInt(arrayKey, 4, 4);
            long c = GetUnsignedInt(arrayKey, 8, 4);
            long d = GetUnsignedInt(arrayKey, 12, 4);

            for (int i=1; i <= 16; i++)
            {
                sum += delta;
                sum &= 0xFFFFFFFF;
                y += ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
                y &= 0xFFFFFFFF;
                z += ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
                z &= 0xFFFFFFFF;
            }

            tmpArray = CopyMemory(tmpArray, 0, y);
            tmpArray = CopyMemory(tmpArray, 4, z);

            tmpOut[0] = tmpArray[3];
            tmpOut[1] = tmpArray[2];
            tmpOut[2] = tmpArray[1];
            tmpOut[3] = tmpArray[0];
            tmpOut[4] = tmpArray[7];
            tmpOut[5] = tmpArray[6];
            tmpOut[6] = tmpArray[5];
            tmpOut[7] = tmpArray[4];

            return tmpOut;
        }


        private static byte[] Encipher(byte[] arrayIn, byte[] arrayKey)
        {
            return Encipher(arrayIn, arrayKey, 0);
        }

        private void Encrypt8Bytes()
        {
            for (_pos = 0; _pos <= 7; _pos++)
            {
                if (_header)
                {
                    _plain[_pos] = (byte)(_plain[_pos] ^ _prePlain[_pos]);
                }
                else
                {
                    _plain[_pos] = (byte)(_plain[_pos] ^ _out[_preCrypt + _pos]);
                }
            }
            byte[] crypted = Encipher(_plain, _key);

            for (int i=0; i <= 7; i++)
            {
                _out[_crypt + i] = crypted[i];
            }

            for (_pos = 0; _pos <= 7; _pos++)
            {
                _out[_crypt + _pos] = (byte)(_out[_crypt + _pos] ^ _prePlain[_pos]);
            }
            _plain.CopyTo(_prePlain, 0);
            _preCrypt = _crypt;
            _crypt = _crypt + 8;
            _pos = 0;
            _header = false;
        }

        private bool Decrypt8Bytes(byte[] arrayIn, long offset)
        {
            //long lngTemp;
            for (_pos = 0; _pos <= 7; _pos++)
            {
                if (_contextStart + _pos > arrayIn.Length - 1)
                {
                    return true;
                }
                _prePlain[_pos] = (byte)(_prePlain[_pos] ^ arrayIn[offset + _crypt + _pos]);
            }
            try
            {
                _prePlain = Decipher(_prePlain, _key);
            }
            catch
            {
                return false;
            }
            //lngTemp = _prePlain.Length - 1;
            _contextStart += 8;
            _crypt += 8;
            _pos = 0;
            return true;
        }

        /*
        private bool Decrypt8Bytes(byte[] arrayIn)
        {
            return Decrypt8Bytes(arrayIn, 0);
        }
        */


        #region Public Methods!


        /// <summary>
        /// QQ TEA 加密函数
        /// </summary>
        /// <param name="arrayIn">要加密的字串</param>
        /// <param name="arrayKey">密钥</param>
        /// <param name="offset">偏移</param>
        /// <returns></returns>
        public byte[] QQEncrypt(byte[] arrayIn, byte[] arrayKey, long offset)
        {
            _plain = new byte[8];
            _prePlain = new byte[8];
            _pos = 1;
            _padding = 0;
            _crypt = _preCrypt = 0;
            arrayKey.CopyTo(_key, 0);    // _key Must Be 16 Length!
            _header = true;
            _pos = 2;
            //计算头部填充字节数
            _pos = (arrayIn.Length + 10) % 8;
            if (_pos != 0)
                _pos = 8 - _pos;
            //输出长度
            _out = new byte[arrayIn.Length + _pos + 10];
            //把POS存到PLAIN的第一个字节
            //0xf8后面3位是空的，正好给Pos
            _plain[0] = (byte)((Rand() & 0xf8) | _pos);
            //用随机数填充1到Pos的内容
            for (int i=1; i <= _pos; i++)
            {
                _plain[i] = (byte)(Rand() & 0xff);
            }
            _pos++;
            _padding = 1;

            //继续填充两个字节随机数，满8字节就加密
            while (_padding < 3)
            {
                if (_pos < 8)
                {
                    _plain[_pos] = (byte)(Rand() & 0xff);
                    _padding++;
                    _pos++;
                }
                else if (_pos == 8)
                {
                    Encrypt8Bytes();
                }
            }

            int I = (int)offset;
            long l = 0;
            //明文内容，满8字节加密到读完
            l = arrayIn.Length;
            while (l > 0)
            {
                if (_pos < 8)
                {
                    _plain[_pos] = arrayIn[I];
                    I++;
                    _pos++;
                    l--;
                }
                else if (_pos == 8)
                {
                    Encrypt8Bytes();
                }
            }

            //末尾填充0，保证是8的倍数
            _padding = 1;
            while (_padding < 9)
            {
                if (_pos < 8)
                {
                    _plain[_pos] = 0;
                    _pos++;
                    _padding++;
                }
                else if (_pos == 8)
                {
                    Encrypt8Bytes();
                }
            }

            return _out;
        }


        public byte[] QQEncrypt(byte[] arrayIn, byte[] arrayKey)
        {
            return QQEncrypt(arrayIn, arrayKey, 0);
        }


        /// <summary>
        ///  QQ TEA 解密函数
        /// </summary>
        /// <param name="arrayIn">要解密字串</param>
        /// <param name="arrayKey">密钥</param>
        /// <param name="offset">偏移</param>
        /// <returns></returns>
        public byte[] QQDecrypt(byte[] arrayIn, byte[] arrayKey, long offset)
        {
            byte[] error = new byte[0];
            //检查是否是8的倍数至少16字节
            if (arrayIn.Length < 16 || (arrayIn.Length % 8 != 0))
            {
                //Return What?
                return error;
            }
            if (arrayKey.Length != 16)
            {
                //Return What?
                return error;
            }
            byte[] m = new byte[offset + 8];
            arrayKey.CopyTo(_key, 0);
            _crypt = _preCrypt = 0;
            //计算消息头部，明文开始的偏移，解密第一字节和7相与得到
            _prePlain = Decipher(arrayIn, arrayKey, offset);
            _pos = _prePlain[0] & 7;
            //计算明文长度
            long count = arrayIn.Length - _pos - 10;
            if (count <= 0)
            {
                //Return What?
                return error;
            }
            _out = new byte[count];
            _preCrypt = 0;
            _crypt = 8;
            _contextStart = 8;
            _pos++;
            _padding = 1;
            //跳过头部
            while (_padding < 3)
            {
                if (_pos < 8)
                {
                    _pos++;
                    _padding++;
                }
                else if (_pos == 8)
                {
                    for (int i=0; i < m.Length; i++)
                        m[i] = arrayIn[i];
                    if (Decrypt8Bytes(arrayIn, offset) == false)
                    {
                        //Return What?
                        return error;
                    }
                }
            }

            //解密明文
            long I = 0;
            while (count != 0)
            {
                if (_pos < 8)
                {
                    _out[I] = (byte)(m[offset + _preCrypt + _pos] ^ _prePlain[_pos]);
                    I++;
                    count--;
                    _pos++;
                }
                else if (_pos == 8)
                {
                    m = arrayIn;
                    _preCrypt = _crypt - 8;
                    if (Decrypt8Bytes(arrayIn, offset) == false)
                    {
                        //Return What?
                        return error;
                    }
                }
            }

            //最后的解密部分，检查尾部是不是0
            for (_padding = 1; _padding <= 7; _padding++)
            {
                if (_pos < 8)
                {
                    if ((m[offset + _preCrypt + _pos] ^ _prePlain[_pos]) != 0)
                    {
                        //Return What?
                        return error;
                    }
                    _pos++;
                }
                else if (_pos == 8)
                {
                    for (int i=0; i < m.Length; i++)
                        m[i] = arrayIn[i];
                    _preCrypt = _crypt;
                    if (Decrypt8Bytes(arrayIn, offset) == false)
                    {
                        //Return What?
                        return error;
                    }
                }
            }
            return _out;
        }

        public byte[] QQDecrypt(byte[] arrayIn, byte[] arrayKey)
        {
            return QQDecrypt(arrayIn, arrayKey, 0);
        }
        #endregion
    }
}
